/**
 * 
 */
package cs342.project1.mathNotebook.tests.expressions;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import cs342.project1.mathNotebook.expressions.AddExpr;
import cs342.project1.mathNotebook.expressions.CosExpr;
import cs342.project1.mathNotebook.expressions.DivExpr;
import cs342.project1.mathNotebook.expressions.EvaluationVisitor;
import cs342.project1.mathNotebook.expressions.Expr;
import cs342.project1.mathNotebook.expressions.LiteralExpr;
import cs342.project1.mathNotebook.expressions.MultExpr;
import cs342.project1.mathNotebook.expressions.NegExpr;
import cs342.project1.mathNotebook.expressions.SinExpr;
import cs342.project1.mathNotebook.expressions.SubExpr;
import cs342.project1.mathNotebook.expressions.TanExpr;
import cs342.project1.mathNotebook.expressions.VarExpr;
import cs342.project1.mathNotebook.expressions.exceptions.UncapturedVariableException;

/**
 * Tests the evaluation visitor class
 * @author Jared Schlicht
 */
public class EvaluationVisitorTests {

	private EvaluationVisitor visitor;
	private static final double delta = 0.00001d;
	
	/**
	 * Initializes the evaluation visitor
	 * @throws java.lang.Exception
	 * @author Jared Schlicht
	 */
	@Before
	public void setUp() throws Exception {
		this.visitor = new EvaluationVisitor();
	}

	/**
	 * Tests basic functionality of addition expression
	 * @author Jared Schlicht
	 * @throws UncapturedVariableException 
	 */
	@Test
	public void testVisitAddExpr() throws UncapturedVariableException {
		Expr addExpr = new AddExpr(new LiteralExpr(3.0), new LiteralExpr(27.0));
		double result = (Double)addExpr.accept(this.visitor);
		assertEquals(30.0d, result, delta);
	}

	/**
	 * Tests basic functionality of cosine expression
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitSubtractionExpr() throws UncapturedVariableException {
		Expr subExpr = new SubExpr(new LiteralExpr(4.5), new LiteralExpr(3.2));
		double result = (Double)subExpr.accept(this.visitor);
		assertEquals(1.3d, result, delta);
	}

	/**
	 * Tests basic multiplication
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitMultiplicationExpr() throws UncapturedVariableException {
		Expr multExpr = new MultExpr(new LiteralExpr(9.0), new LiteralExpr(3.0));
		double result = (Double)multExpr.accept(this.visitor);
		assertEquals(27.0d, result, delta);
	}

	/**
	 * Tests basic division
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitDivisionExpr() throws UncapturedVariableException {
	    Expr divExpr = new DivExpr(new LiteralExpr(9.0), new LiteralExpr(3.0));
	    double result = (Double)divExpr.accept(this.visitor);
	    assertEquals(3.0d, result, delta);
	}

	/**
	 * Tests basic sine function
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitSineExpr() throws UncapturedVariableException {
		Expr sinExpr = new SinExpr(new LiteralExpr(45.0));
		double result = (Double)sinExpr.accept(this.visitor);
		assertEquals(0.850903525d, result, delta);
	}

	/**
	 * Tests basic cosine function
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitCosineExpr() throws UncapturedVariableException {
	    Expr cosExpr = new CosExpr(new LiteralExpr(45.0));
	    double result = (Double)cosExpr.accept(this.visitor);
	    assertEquals(0.525321989d, result, delta);
	}

	/**
	 * Tests a basic tangent expression
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitTangentExpr() throws UncapturedVariableException {
	    Expr tanExpr = new TanExpr(new LiteralExpr(45.0));
	    double result = (Double)tanExpr.accept(this.visitor);
	    assertEquals(1.61977519d, result, delta);
	}

	/**
	 * Tests captured variables are properly evaluated
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitVariable() throws UncapturedVariableException {
	    this.visitor = new EvaluationVisitor("x", 4.0);
	    Expr varExpr = new VarExpr("x");
	    double result = (Double)varExpr.accept(this.visitor);
	    assertEquals(4.0d, result, delta);
	}

	/**
	 * Ensures an uncaptured variable throws an exception as expected
	 * @author Jared Schlicht
	 * @throws UncapturedVariableException
	 */
	@Test(expected=UncapturedVariableException.class)
	public void testVisitUncapturedVariable() throws UncapturedVariableException {
	    this.visitor = new EvaluationVisitor("x", 4.0);
	    Expr varExpr = new VarExpr("a");
	    double result = (Double)varExpr.accept(this.visitor);
	}
	
	/**
	 * Tests a negation operation
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitNegationExpr() throws UncapturedVariableException {
	    Expr negExpr = new NegExpr(new LiteralExpr(3.0));
	    double result = (Double)negExpr.accept(this.visitor);
	    assertEquals(-3.0, result, delta);
	}

	/**
	 * Tests a basic literal number
	 * 
	 * @author Jared Schlicht
	 */
	@Test
	public void testVisitLiteralNumberExpr() throws UncapturedVariableException {
	    Expr litExpr = new LiteralExpr(3.0);
	    double result = (Double)litExpr.accept(this.visitor);
	    assertEquals(3.0, result, delta);
	}

	/**
	 * Tests a nested expression: (3*4)+5
	 * 
	 * @throws UncapturedVariableException
	 */
	@Test
	public void testComplexEspression() throws UncapturedVariableException {
	    Expr a = new LiteralExpr(3.0);
	    Expr b = new LiteralExpr(4.0);
	    Expr c = new LiteralExpr(5.0);
	    
	    Expr multExpr = new MultExpr(a,b);
	    Expr addExpr = new AddExpr(multExpr, c);
	    
	    double result = (Double)addExpr.accept(this.visitor);
	    assertEquals(17.0, result, delta);
	}
	
}
